#pragma once
#include "ControlBase.h"

class ComboBox : public Control
{
#define COMBO_MIN_SCROLL_BLOCK 16
private:
	int _underMouseIndex = -1;

public:
	virtual UIClass Type() { return UIClass::UI_ComboBox; }
	bool IsContainer() override
	{
		return false;
	}
	D2D1_COLOR_F UnderMouseBackColor = Colors::SkyBlue;
	D2D1_COLOR_F UnderMouseForeColor = Colors::White;
	D2D1_COLOR_F ScrollBackColor = Colors::LightGray;
	D2D1_COLOR_F ScrollForeColor = Colors::DimGrey;
	D2D1_COLOR_F ButtonBackColor = Colors::SkyBlue;
	UINT32 ExpandCount = 4;
	UINT32 ExpandScroll = 0;
	bool Expand = false;
	int SelectedIndex = 0;
	List<std::wstring> values;
	float Boder = 1.5f;
	ComboBox(std::wstring text, int x, int y, int width = 120, int height = 24)
	{
		this->Text = text;
		this->Location = POINT{x, y};
		this->Size = SIZE{width, height};
		this->BackColor = D2D1_COLOR_F{0.75f, 0.75f, 0.75f, 0.75f};
	}
	SIZE ActualSize() override
	{
		auto size = this->Size;
		if (this->Expand)
		{
			size.cy += this->Height * this->ExpandCount;
		}
		return size;
	}

	void DrawScroll()
	{
		auto d2d = this->Render;
		auto abslocation = this->AbsLocation;
		auto font = this->Font ? this->Font : d2d->DefaultFontObject;
		auto size = this->ActualSize();

		if (this->values.Count > 0)
		{
			float _render_width = this->Width - 8;
			float _render_height = this->Height * this->ExpandCount;
			float font_height = d2d->GetTextSize(L"I", font).height;
			float row_height = font_height + 2.0f;
			int render_count = this->ExpandCount;
			if (render_count < this->values.Count)
			{
				int max_scroll = this->values.Count - render_count;
				float scroll_block_height = ((float)render_count / (float)this->values.Count) * (float)_render_height;
				if (scroll_block_height < COMBO_MIN_SCROLL_BLOCK)
					scroll_block_height = COMBO_MIN_SCROLL_BLOCK;

				float scroll_block_move_space = _render_height - scroll_block_height;
				float yt = scroll_block_height * 0.5f;
				float yb = _render_height - (scroll_block_height * 0.5f);
				float per = (float)this->ExpandScroll / (float)max_scroll;
				float scroll_tmp_y = per * scroll_block_move_space;

				float scroll_block_top = scroll_tmp_y;
				d2d->FillRoundRect(abslocation.x + (this->Width - 8.0f), abslocation.y + this->Height, 8.0f, _render_height, this->ScrollBackColor, 4.0f);
				d2d->FillRoundRect(abslocation.x + (this->Width - 8.0f), abslocation.y + scroll_block_top + this->Height, 8.0f, scroll_block_height, this->ScrollForeColor, 4.0f);
			}
		}
	}
	void Update() override
	{
		if (this->IsVisual == false)
			return;
		bool isUnderMouse = this->MostParent->UnderMouse == this;
		bool isSelected = this->MostParent->Selected == this;
		auto d2d = this->Render;
		auto abslocation = this->AbsLocation;
		auto size = this->ActualSize();
		auto absRect = this->AbsRect;
		d2d->PushDrawRect(absRect.left, absRect.top, absRect.right - absRect.left, absRect.bottom - absRect.top);
		{
			Control *tmpc = this;
			List<Control *> need_render_background;
			while (tmpc->BackColor.a < 1.0f)
			{
				tmpc = tmpc->Parent;
				need_render_background.Add(tmpc);
			}
			if (this->BackColor.a < 1.0f)
			{
				for (int i = need_render_background.Count - 1; i >= 0; i--)
				{
					d2d->FillRect(abslocation.x, abslocation.y, size.cx, size.cy, need_render_background[i]->BackColor);
					if (need_render_background[i]->Image)
					{
						need_render_background[i]->RenderImage();
					}
				}
			}
			d2d->FillRect(abslocation.x, abslocation.y, size.cx, size.cy, this->BackColor);
			if (this->Image)
			{
				this->RenderImage();
			}
			auto font = this->Font ? this->Font : d2d->DefaultFontObject;
			auto textSize = font->GetTextSize(this->Text);
			float drawLeft = 0.0f;
			float drawTop = 0.0f;
			if (this->Height > textSize.height)
			{
				drawLeft = drawTop = (this->Height - textSize.height) / 2.0f;
			}
			d2d->DrawString(this->Text, abslocation.x + drawLeft, abslocation.y + drawTop, this->ForeColor, this->Font);
			float exbL = abslocation.x + (size.cx - this->Height);
			d2d->FillRect(exbL, abslocation.y, this->Height, this->Height, this->ButtonBackColor);
			if (this->Expand)
			{
				float exbT = abslocation.y;
				float recSize = this->Height;
				float tmpb = recSize * 0.3f;
				float tmpc = recSize * 0.7f;
				d2d->FillPolygon(
					{{exbL + tmpb, exbT + tmpb},
					 {exbL + tmpc, exbT + tmpb},
					 {exbL + (recSize * 0.5f), exbT + tmpc},
					 {exbL + tmpb, exbT + tmpb}},
					this->ForeColor);
			}
			else
			{
				float exbT = abslocation.y;
				float recSize = this->Height;
				float tmpb = recSize * 0.3f;
				float tmpc = recSize * 0.7f;
				d2d->FillPolygon(
					{{exbL + tmpb, exbT + tmpb},
					 {exbL + tmpc, exbT + (recSize * 0.5f)},
					 {exbL + tmpb, exbT + tmpc},
					 {exbL + tmpb, exbT + tmpb}},
					this->ForeColor);
			}
			if (this->Expand)
			{
				for (int i = this->ExpandScroll; i < this->ExpandScroll + this->ExpandCount && i < this->values.Count; i++)
				{
					if (i == _underMouseIndex)
					{
						int viewIndex = i - this->ExpandScroll;
						d2d->FillRect(abslocation.x,
									  abslocation.y + ((viewIndex + 1) * this->Height),
									  this->Width, this->Height, this->UnderMouseBackColor);
						d2d->DrawString(
							this->values[i],
							abslocation.x + drawLeft,
							abslocation.y + drawTop + (((i - this->ExpandScroll) + 1) * this->Height),
							UnderMouseForeColor, this->Font);
					}
					else
					{
						d2d->DrawString(
							this->values[i],
							abslocation.x + drawLeft,
							abslocation.y + drawTop + (((i - this->ExpandScroll) + 1) * this->Height),
							this->ForeColor, this->Font);
					}
				}
				this->DrawScroll();
				d2d->DrawRect(abslocation.x, abslocation.y, size.cx, this->Height, this->BolderColor, this->Boder);
			}
			d2d->DrawRect(abslocation.x, abslocation.y, size.cx, size.cy, this->BolderColor, this->Boder);
		}
		d2d->PopDrawRect();
	}

	bool ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam, int xof, int yof) override
	{
		if (WM_LBUTTONDOWN == message)
		{
			if (this->MostParent->Selected && this->MostParent->Selected != this)
			{
				auto se = this->MostParent->Selected;
				this->MostParent->Selected = this;
				se->SingleUpdate();
			}
		}
		switch (message)
		{
		case WM_DROPFILES:
		{
			HDROP hDropInfo = HDROP(wParam);
			UINT uFileNum = DragQueryFile(hDropInfo, 0xffffffff, NULL, 0);
			TCHAR strFileName[MAX_PATH];
			List<std::wstring> files;
			for (UINT i = 0; i < uFileNum; i++)
			{
				DragQueryFile(hDropInfo, i, strFileName, MAX_PATH);
				files.Add(strFileName);
			}
			DragFinish(hDropInfo);
			if (files.Count > 0)
			{
				this->OnDropFile(this, files);
			}
		}
		break;
		case WM_MOUSEWHEEL: // mouse wheel
		{
			if (this->Expand)
			{
				if (GET_WHEEL_DELTA_WPARAM(wParam) > 0)
				{
					if (this->ExpandScroll > 0)
					{
						this->ExpandScroll -= 1;
						this->SingleUpdate();
					}
				}
				else
				{
					if (this->ExpandScroll < this->values.Count - this->ExpandCount)
					{
						this->ExpandScroll += 1;
						this->SingleUpdate();
					}
				}
			}
			else
			{
				if (GET_WHEEL_DELTA_WPARAM(wParam) > 0)
				{
					if (this->SelectedIndex > 0)
					{
						this->SelectedIndex -= 1;
						this->Text = this->values[this->SelectedIndex];
						this->SingleUpdate();
					}
				}
				else
				{
					if (this->SelectedIndex < this->values.Count - 1)
					{
						this->SelectedIndex += 1;
						this->Text = this->values[this->SelectedIndex];
						this->SingleUpdate();
					}
				}
			}
			MouseEventArgs event_obj = MouseEventArgs(MouseButtons::MouseButtons_None, 0, xof, yof, GET_WHEEL_DELTA_WPARAM(wParam));
			this->OnMouseWheel(this, event_obj);
		}
		break;
		case WM_MOUSEMOVE: // mouse move
		{
			this->MostParent->UnderMouse = this;
			if (this->Expand)
			{
				bool need_update = false;
				if (xof >= 0 && yof >= this->Height)
				{
					int _yof = int((yof - this->Height) / this->Height);
					if (_yof <= this->ExpandCount)
					{
						int idx = _yof + this->ExpandScroll;
						if (idx < this->values.Count)
						{
							if (idx != this->_underMouseIndex)
							{
								need_update = true;
							}
							this->_underMouseIndex = idx;
						}
					}
				}
				if (need_update)
					this->SingleUpdate();
			}
			MouseEventArgs event_obj = MouseEventArgs(MouseButtons::MouseButtons_None, 0, xof, yof, HIWORD(wParam));
			this->OnMouseMove(this, event_obj);
		}
		break;
		case WM_LBUTTONDOWN: // mouse down
		case WM_RBUTTONDOWN:
		case WM_MBUTTONDOWN:
		{
			if (WM_LBUTTONDOWN == message)
			{
				this->MostParent->Selected = this;
			}
			MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
			this->OnMouseDown(this, event_obj);
		}
		break;
		case WM_LBUTTONUP: // mouse up
		case WM_RBUTTONUP:
		case WM_MBUTTONUP:
		{
			if (WM_LBUTTONUP == message && this->MostParent->Selected == this)
			{
				if (xof >= 0 && yof >= 0)
				{
					if (yof > 0 && yof < this->Width)
					{
						if (xof > (this->Width - this->Height))
						{
							this->Expand = !this->Expand;
							this->MostParent->Update();
							this->SingleUpdate();
							if (this->Expand)
							{
								this->MostParent->ForeGroundControls.Add(this);
							}
							else
							{
								this->MostParent->ForeGroundControls.Remove(this);
							}
							this->MostParent->Selected = NULL;
							MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
							this->OnMouseUp(this, event_obj);
							this->SingleUpdate();
							break;
						}
						if (this->Expand)
						{
							int _yof = int((yof - this->Height) / this->Height);
							if (_yof <= this->ExpandCount)
							{
								int idx = _yof + this->ExpandScroll;
								if (idx < this->values.Count)
								{
									this->_underMouseIndex = idx;
									this->SelectedIndex = this->_underMouseIndex;
									this->Text = this->values[this->SelectedIndex];
									this->Expand = false;
									this->MostParent->Update();
									this->SingleUpdate();
								}
							}
						}
					}
				}
			}
			this->MostParent->Selected = NULL;
			MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
			this->OnMouseUp(this, event_obj);
			this->SingleUpdate();
		}
		break;
		case WM_LBUTTONDBLCLK: // mouse double click
		{
			MouseEventArgs event_obj = MouseEventArgs(FromParamToMouseButtons(message), 0, xof, yof, HIWORD(wParam));
			this->OnMouseDoubleClick(this, event_obj);
		}
		break;
		case WM_KEYDOWN: // keyboard down
		{
			KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
			this->OnKeyDown(this, event_obj);
		}
		break;
		case WM_KEYUP: // keyboard up
		{
			KeyEventArgs event_obj = KeyEventArgs((Keys)(wParam | 0));
			this->OnKeyUp(this, event_obj);
		}
		break;
		}
		return true;
	}
};
